import os
import shutil
from datetime import datetime
from typing import List, Optional, Dict
from langchain.text_splitter import Language, RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader, CSVLoader, UnstructuredMarkdownLoader, \
    BSHTMLLoader, UnstructuredPowerPointLoader, UnstructuredExcelLoader, \
    Docx2txtLoader
from langchain_core.documents import Document
from loguru import logger

# 代码语言类型映射字典
code_language_type_map = {
    "go": Language.GO,
    "py": Language.PYTHON,
    "java": Language.JAVA,
    "kotlin": Language.KOTLIN,
    "js": Language.JS,
    "ts": Language.TS,
    "php": Language.PHP,
    "proto": Language.PROTO,
    "rst": Language.RST,
    "ruby": Language.RUBY,
    "rust": Language.RUST,
    "scala": Language.SCALA,
    "swift": Language.SWIFT,
    "markdown": Language.MARKDOWN,
    "latex": Language.LATEX,
    "html": Language.HTML,
    "sol": Language.SOL,
    "csharp": Language.CSHARP,
    "cobol": Language.COBOL,
    'md': Language.MARKDOWN,
    "css": Language.HTML,
    "vue": Language.HTML,
    "scss": Language.HTML,
    "sass": Language.HTML,
    "less": Language.HTML,
    "tsx": Language.TS,
    "jsx": Language.JS,
}

# 支持的代码文件扩展名列表
code_file_extensions = [
    # Programming languages
    'py', 'java', 'cpp', 'c', 'h', 'hpp', 'cs', 'js', 'ts', 'go', 'rb', 'php',
    'swift', 'kt', 'scala', 'rs', 'r', 'm', 'mm', 'f90', 'f95', 'f03',
    'sql', 'pl', 'pm', 't', 'psgi', 'lua', 'groovy', 'dart', 'pas',

    # Web development
    'html', 'htm', 'css', 'scss', 'sass', 'less', 'jsx', 'tsx', 'vue', 'svelte',
    'json', 'xml', 'yaml', 'yml', 'graphql', 'gql',

    # Scripts
    'sh', 'bash', 'zsh', 'fish', 'ps1', 'bat', 'cmd', 'vbs', 'awk', 'sed',

    # Configuration files
    'ini', 'conf', 'cfg', 'config', 'properties', 'env', 'toml', 'dockerfile',
    'gitignore', 'npmrc', 'babelrc', 'eslintrc', 'prettierrc',

    # Build and package files
    'gradle', 'maven', 'pom', 'sbt', 'ant', 'make', 'cmake', 'cabal',
    'gemspec', 'podspec', 'cargo', 'nuspec', 'csproj', 'vcxproj',

    # Documentation and text files
    'txt', 'md',

    # Database files
    'sql', 'sqlite', 'db', 'mdb', 'accdb', 'dbf', 'mdf', 'ndf', 'bak',
    'frm', 'myd', 'myi', 'ibd', 'pdb',

    # Binary data files
    'dat', 'bin', 'hex', 'dump',

    # Log files
    'log', 'logs', 'trace', 'out', 'err',

    # WeChat Mini Program files
    'wxml', 'wxss', 'js', 'json', 'png', 'jpg', 'jpeg', 'gif',
]

# 支持的图片文件扩展名列表
image_file_extensions = ["jpg", "jpeg", "png", "bmp", "gif"]

# 支持的音频文件扩展名列表
audio_file_extensions = ["mp3", "wav", "ogg", "m4a", "flac", "aac", "wma"]

# 支持的视频文件扩展名列表
video_file_extensions = ["mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v", "mpeg", "mpg"]

# 支持的文档文件扩展名列表
document_file_extensions = [
    # Microsoft Office
    'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'vsd', 'vsdx', 'pub',
    # PDF & PostScript
    'pdf', 'ps', 'eps',
    # eBook
    'epub', 'mobi', 'azw', 'azw3',
    # Archive & Compressed Documents
    'zip', 'rar', '7z', 'tar', 'gz', 'bz2',
    # Other Document Types
    'pages', 'numbers', 'key',  # Apple iWork
]

# 读取文件内容，自动检测编码
def read_file_content(file_path: str, max_lines: int = -1) -> Optional[str]:
    """
    读取文件内容，自动检测编码

    Args:
        file_path: 文件路径
        max_lines: 最大读取行数，-1表示读取全部内容，默认为-1

    Returns:
        文件内容，如果读取失败返回None
    """
    try:
        detected_encoding = detect_encoding(file_path)
        with open(file_path, 'r', encoding=detected_encoding, errors='replace') as file:
            if max_lines == -1:
                content = file.read()
            else:
                lines = []
                for i, line in enumerate(file):
                    if i >= max_lines:
                        break
                    lines.append(line)
                content = ''.join(lines)
        return content
    except Exception as e:
        logger.error(f"读取文件时发生错误：{e}")
        return None

# 检测文件编码格式
def detect_encoding(file_path: str) -> str:
    """
    检测文件编码格式

    Args:
        file_path: 文件路径

    Returns:
        检测到的编码格式，默认返回utf-8
    """
    try:
        import chardet
        with open(file_path, 'rb') as raw_file:
            result = chardet.detect(raw_file.read())
        return result.get('encoding', 'utf-8') or 'utf-8'
    except Exception as e:
        logger.warning(f"检测文件编码失败，使用默认编码utf-8：{e}")
        return 'utf-8'


# 重命名文件
def rename_file(file_path: str, new_path: str) -> bool:
    """
    重命名文件

    Args:
        file_path: 原文件路径
        new_path: 新文件路径

    Returns:
        是否重命名成功
    """
    try:
        os.rename(file_path, new_path)
        logger.info(f"重命名文件{file_path}, to {new_path}")
        return True
    except Exception as e:
        logger.error(f"重命名文件时发生错误：{e}")
        return False


# 覆写文件内容，可选是否备份
def overwrite_file_content(file_path: str, content: str, backup: bool = True) -> bool:
    """
    覆写文件内容，可选是否备份

    Args:
        file_path: 文件路径
        content: 新内容
        backup: 是否备份原文件

    Returns:
        是否写入成功
    """
    try:
        if backup and os.path.exists(file_path):
            backup_path = file_path + "." + str(int(datetime.now().timestamp()))
            shutil.copy(file_path, backup_path)

        with open(file_path, 'w', encoding='utf-8') as file:
            file.write(content)
        return True
    except Exception as e:
        logger.error(f"写入文件时发生错误：{e}")
        return False

# 获取文件类型（扩展名）
def get_file_type(string: str) -> Optional[str]:
    """
    获取文件类型（扩展名）

    Args:
        string: 文件路径或文件名

    Returns:
        文件扩展名，如果没有扩展名返回None
    """
    if not string:
        return None

    file_type = os.path.splitext(string)[1][1:]
    if not file_type:
        return None
    else:
        return file_type.strip().lower()


# 获取文件的编程语言类型
def get_language_type(file_extension: str) -> Optional[Language]:
    """
    获取文件的编程语言类型

    Args:
        file_extension: 文件扩展名

    Returns:
        Language类型，如果不是编程语言文件返回None
    """
    if not file_extension:
        return None
    return code_language_type_map.get(file_extension.lower(), None)


# 判断是否为编程语言文件
def is_language_file(string: str) -> bool:
    """
    判断是否为编程语言文件

    Args:
        string: 文件路径或文件名

    Returns:
        是否为编程语言文件
    """
    if string and get_language_type(get_file_type(string)):
        return True
    return False

def is_doc_file(string: str) -> bool:
    """
    判断是否为文档文件

    Args:
        string: 文件路径或文件名

    Returns:
        是否为文档文件
    """
    if not string:
        return False
    ext = get_file_type(string)
    if ext and ext.lower() in document_file_extensions:
        return True
    return False

# 判断是否为程序文件
def is_program_file(string: str) -> bool:
    """
    判断是否为程序文件

    Args:
        string: 文件路径或文件名

    Returns:
        是否为程序文件
    """
    if not string:
        return False
    ext = get_file_type(string)
    if ext and ext.lower() in code_file_extensions:
        return True
    return False

# 判断是否为图片文件
def is_image(string: str) -> bool:
    """
    判断是否为图片文件

    Args:
        string: 文件路径或文件名

    Returns:
        是否为图片文件
    """
    if not string:
        return False
    ext = get_file_type(string)
    if ext and ext.lower() in image_file_extensions:
        return True
    return False

# 判断是否为音频文件
def is_audio(string: str) -> bool:
    """
    判断是否为音频文件

    Args:
        string: 文件路径或文件名

    Returns:
        是否为音频文件
    """
    if not string:
        return False
    ext = get_file_type(string)
    if ext and ext.lower() in audio_file_extensions:
        return True
    return False

# 判断是否为视频文件
def is_video(string: str) -> bool:
    """
    判断是否为视频文件

    Args:
        string: 文件路径或文件名

    Returns:
        是否为视频文件
    """
    if not string:
        return False
    ext = get_file_type(string)
    if ext and ext.lower() in video_file_extensions:
        return True
    return False

def clear_blank_line(content: str) -> str:
    """
    清理空白行

    Args:
        content: 文本内容

    Returns:
        清理后的内容
    """
    if not content:
        return ""
    lines = content.split('\n')
    cleaned_lines = [line for line in lines if line.strip()]
    return '\n'.join(cleaned_lines)

def clear_line_fields(content: str) -> str:
    """
    清理行字段

    Args:
        content: 文本内容

    Returns:
        清理后的内容
    """
    if not content:
        return ""
    # 简单的字段清理，可以根据需要扩展
    return content.strip()

def load_and_split_file(file_path: str,
                        chunk_size: int = 1500,
                        chunk_overlap: int = 50,
                        ) -> Optional[List[str]]:
    """
    加载并分割文件

    Args:
        file_path: 文件路径
        chunk_size: 块大小
        chunk_overlap: 块重叠大小

    Returns:
        分割后的文本块列表，如果加载失败返回None
    """
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"File {file_path} not found")

    file_type = get_file_type(file_path)
    if file_type is None:
        logger.warning(f"无法确定文件类型: {file_path}")
        return None

    language = get_language_type(file_type)
    if language is not None:
        content = read_file_content(file_path)
        if content is None:
            return None
        return split_text_into_chunks(content,
                                      chunk_size,
                                      chunk_overlap,
                                      language)

    if is_image(file_path):
        # 图片文件处理逻辑，这里暂时返回None
        # 可以集成OCR或图片描述功能
        logger.info(f"图片文件暂不支持处理: {file_path}")
        return None

    loader = None
    try:
        if "pdf" == file_type:
            loader = PyPDFLoader(file_path=file_path, extract_images=False)
        elif "csv" == file_type:
            loader = CSVLoader(file_path=file_path)
        elif file_type in ['xlsx', 'xls']:
            loader = UnstructuredExcelLoader(file_path=file_path)
        elif file_type in ['docx', 'doc']:
            loader = Docx2txtLoader(file_path)
        elif file_type in ['ppt', 'pptx']:
            loader = UnstructuredPowerPointLoader(file_path=file_path)
        elif "html" == file_type:
            loader = BSHTMLLoader(file_path=file_path)
        elif "md" == file_type:
            loader = UnstructuredMarkdownLoader(file_path=file_path)

        if loader is not None:
            documents = loader.load()
        else:
            content = read_file_content(file_path)
            if content is None:
                return None
            documents = [Document(page_content=content)]

        if chunk_size <= 0:
            return [doc.page_content for doc in documents]
        else:
            if file_type in ['csv', 'xlsx', 'xls']:
                # 对于表格文件，合并所有内容
                combined_content = "\n".join([clear_line_fields(doc.page_content) for doc in documents])
                return split_text_into_chunks(combined_content, chunk_size, chunk_overlap)
            else:
                # 清理空白行并合并所有文档内容
                all_content = []
                for doc in documents:
                    cleaned_content = clear_blank_line(doc.page_content)
                    all_content.append(cleaned_content)
                combined_content = "\n".join(all_content)
                return split_text_into_chunks(combined_content, chunk_size, chunk_overlap)

    except Exception as e:
        logger.error(f"Load file {file_path} error: {e}")
        raise


def split_text_into_chunks(content: str = None,
                           chunk_size: int = 1000,
                           chunk_overlap: int = 50,
                           language: Optional[Language] = None,
                           splitter: type = RecursiveCharacterTextSplitter) -> List[str]:
    """
    将文本分割成块

    Args:
        content: 文本内容
        chunk_size: 块大小
        chunk_overlap: 块重叠大小
        language: 编程语言类型
        splitter: 分割器类型

    Returns:
        分割后的文本块列表
    """
    if content is None or len(content) == 0:
        return []

    try:
        if language is None:
            text_splitter = splitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
        else:
            text_splitter = RecursiveCharacterTextSplitter.from_language(
                chunk_size=chunk_size,
                language=language,
                chunk_overlap=chunk_overlap
            )
        return text_splitter.split_text(content)
    except Exception as e:
        logger.error(f"分割文本时发生错误: {e}")
        # 如果特定语言分割失败，回退到通用分割器
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=chunk_size,
            chunk_overlap=chunk_overlap
        )
        return text_splitter.split_text(content)


def get_files_with_preview(file_dir: str = ".", file_types: Optional[List[str]] = None, recursive: bool = True, preview_lines: int = 3, base_dir: str = ".") -> List[Dict[str, str]]:
    """
    获取文档目录下所有文件的内容列表及前几行内容

    Args:
        file_dir: 文档目录路径，默认为"doc"
        file_types: 要搜索的文件类型列表，如['.py', '.md', '.txt']，None表示所有文件
        recursive: 是否递归搜索子目录，默认为True
        preview_lines: 文件预览的行数，默认为3行
        base_dir: 基础目录路径，默认为当前目录

    Returns:
        文件列表，每个元素包含filename和file_preview字段
    """
    # 参数验证
    if not isinstance(file_dir, str) or not file_dir.strip():
        return []

    if preview_lines < 0:
        preview_lines = 3

    # 构建完整路径
    full_doc_dir = os.path.join(base_dir, file_dir) if not os.path.isabs(file_dir) else file_dir

    # 路径验证
    if not os.path.exists(full_doc_dir):
        return []

    if not os.path.isdir(full_doc_dir):
        return []

    try:
        file_summaries = []

        def _process_file(file_path: str, relative_name: str) -> Optional[Dict[str, str]]:
            """处理单个文件的公共逻辑"""
            try:
                # 检查文件类型过滤
                if file_types:
                    file_ext = os.path.splitext(relative_name)[1].lower()
                    if file_ext not in file_types:
                        return None

                # 检查文件是否可读
                if not os.access(file_path, os.R_OK):
                    logger.warning(f"文件无读取权限: {file_path}")
                    return None

                # 获取文件大小，跳过过大的文件
                file_size = os.path.getsize(file_path)
                if file_size > 10 * 1024 * 1024:  # 10MB
                    logger.warning(f"文件过大，跳过: {file_path} ({file_size} bytes)")
                    return None

                # 获取文件预览内容
                preview_content = read_file_content(file_path, max_lines=preview_lines)
                if preview_content is None:
                    preview_content = "无法读取文件内容"
                elif not preview_content.strip():
                    preview_content = "文件内容为空"
                else:
                    preview_content = preview_content.strip()

                # 返回字典格式
                return {
                    "filename": relative_name,
                    "file_preview": preview_content
                }

            except Exception as e:
                logger.warning(f"处理文件时出错 {file_path}: {e}")
                return None

        # 根据recursive参数决定遍历方式
        if recursive:
            # 递归遍历目录下的所有文件
            for root, dirs, files in os.walk(full_doc_dir):
                # 跳过隐藏目录
                dirs[:] = [d for d in dirs if not d.startswith('.')]

                for file in files:
                    # 跳过隐藏文件
                    if file.startswith('.'):
                        continue

                    file_path = os.path.join(root, file)
                    relative_path = os.path.relpath(file_path, full_doc_dir)

                    file_info = _process_file(file_path, relative_path)
                    if file_info:
                        file_summaries.append(file_info)
        else:
            # 只遍历当前目录
            try:
                for file in os.listdir(full_doc_dir):
                    # 跳过隐藏文件
                    if file.startswith('.'):
                        continue

                    file_path = os.path.join(full_doc_dir, file)
                    if os.path.isfile(file_path):
                        file_info = _process_file(file_path, file)
                        if file_info:
                            file_summaries.append(file_info)
            except PermissionError:
                logger.error(f"没有权限访问目录: {full_doc_dir}")
                return []

        return file_summaries

    except Exception as e:
        logger.error(f"遍历文档目录失败: {str(e)}")
        return []
